function genUUID() {
  let d = new Date().getTime();

  if (window.performance && typeof window.performance.now === 'function') {
    d += performance.now(); //use high-precision timer if available
  }

  const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
    /[xy]/g,
    function (c) {
      var r = (d + Math.random() * 16) % 16 | 0;
      d = Math.floor(d / 16);
      return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16)
    }
  );

  return uuid
}

const name = "sman";

const Log = {
  log (msg) {
    console.log(`${name}: ${msg}`);
  }
};

// 统计 xhr 耗时
function initXhr(ctx) {
  const MXMLHttpRequest = window.XMLHttpRequest;

  window.XMLHttpRequest = function (...arg) {
    const request = new MXMLHttpRequest(...arg);

    let url = "";

    /**
     * 这里代理 open，是为了获取请求 url
     * 否则在 error 事件处理器中无法得到 responseURL
     */
    let open = request.open;
    request.open = function (...arg) {
      url = arg[1];
      open.call(request, ...arg);
    };

    const start_time = Date.now();

    /**
     * 请求已经完成
     * 服务端可能响应 2xx 3xx 4xx 5xx 等等
     */
    request.addEventListener('load', function (e) {
      const xmlhttp = e.target;
      const end_time = Date.now();
  
      Log.log(`xhr load ${url} use ${end_time - start_time}ms`);

      ctx.sendXhrEvent({
        start_time: start_time,
        end_time: end_time,
        spend_time: end_time - start_time,
        url: url,
        http_code: xmlhttp.status // 200
      });
    });

    /**
     * 请求没有得到处理
     * 可能是服务器挂了
     */
    request.addEventListener('error', function(e) {
      Log.log(`xhr error ${url}`);

      const xmlhttp = e.target;
      const end_time = Date.now();

      ctx.sendXhrEvent({
        start_time: start_time,
        end_time: end_time,
        spend_time: end_time - start_time,
        url: url,
        http_code: xmlhttp.status // 0
      });
    });

    return request;
  };
}

function initErrorEvent(ctx) {
  function resolveStack(e) {
    const frames = [];
    const list = e.error.stack.split('\n');

    list.forEach((element, index) => {
      if (index !== 0) {
        const reg = /^(    at)( .*)?( .*)$/;
        let [, , arg2, arg3] = reg.exec(element);
        let fun = arg2 ? arg2.replace(/\s/g, '') : '?';

        const [protol, filename, lineno, colno] = arg3
          .replace(/\s/g, '')
          .replace('(', '')
          .replace(')', '')
          .split(':');

        frames.push({
          filename: protol + ':' + filename, // 文件名
          colno: +colno, // 列数
          function: fun, // 调用函数
          lineno: +lineno, // 行数
        });
      }
    });
    return frames
  }
  window.addEventListener(
    'error',
    function (e) {
      const target = e.target;
      if (target === window) return

      ctx.sendErrorEevent({
        type: 'resource',
        value: (target.src || target.href) + ' load error',
        message: (target.src || target.href) + ' load error',
      });
    },
    true
  );
  window.addEventListener('unhandledrejection', function (e) {
    Log.log('unhandledrejection');
    if (e.reason.isAxiosError) return

    ctx.sendErrorEevent({
      type: 'unhandledrejection',
      value: (e.reason && e.reason.msg) || e.reason || '',
      message: (e.reason && e.reason.msg) || e.reason || '',
    });
  });

  /**
   * 常规JS错误
   */
  window.addEventListener('error', function (e) {
    Log.log('error');
    const message = e.message;
    const [type, value] = message.replace('Uncaught ', '').split(': ');

    ctx.sendErrorEevent({
      type: type,
      value: value,
      message: message,
      stacktrace: {
        frames: resolveStack(e),
      },
    });
  });
}

function initUser(ctx) {
  const userId = localStorage.getItem('sman_user_id');
  if (!userId) {
    window.localStorage.setItem('sman_user_id', genUUID());
  }
}

function getUserId() {
  return window.localStorage.getItem('sman_user_id')
}

/**
 * 获取性能信息
 */
const getPerformance = () => {
  const timing = window.performance.timing;
  return {
    redirect_time: timing.redirectEnd - timing.redirectStart,
    request_time: timing.responseEnd - timing.requestStart,
    dom_time: timing.domComplete - timing.domLoading,
    load_time: timing.loadEventEnd - timing.navigationStart,
    timing: timing
  }
};

function initPerformance(ctx) {
  window.addEventListener('load', () => {
    const t = getPerformance();
    console.log(t);
    ctx.sendPageView(t);
  });
}

let config = {};
let axios = null;

const sman = {
  init(opt) {
    config = opt;
    axios = config.axios;

    initUser();
    initXhr(this);
    initErrorEvent(this);
    initPerformance(this);

    // SDK 数据上报接口URL
    this.addErrorUrl = opt.url + '/report/error';
    this.addGatewayUrl = opt.url + '/report/gateway';
    this.addPageViewUrl = opt.url + '/report/pageview';

    // 以下URL的请求不上报，避免死循环
    this.ajaxWhiteUrl = [
      this.addErrorUrl,
      this.addGatewayUrl,
      this.addPageViewUrl,
    ];
  },

  /**
   * @desc 是否为上报系统的接口URL
   */
  isWhiteUrl(url) {
    return Boolean(this.ajaxWhiteUrl.find((it) => url.indexOf(it) === 0))
  },

  // 获取公共参数
  getCommonParams() {
    return {
      appkey: config.appkey,
      event_id: genUUID(),
      user_id: getUserId(),
      platform: 'javascript',
      time: Date.now(),
      request: {
        url: window.location.href,
        'User-Agent': window.navigator.userAgent,
      },
    }
  },

  /**
   * @description JS异常|资源加载异常
   * @param {*} exception
   */
  sendErrorEevent(exception) {
    const payload = this.getCommonParams();
    payload.exception = exception;
    axios({
      url: this.addErrorUrl,
      method: 'POST',
      data: payload,
      headers: {
        is_report: 1,
      },
    }).catch((e) => {
      Log.log(`report error: ${e}`);
    });
  },

  // 页面性能
  sendPageView(params) {
    const payload = this.getCommonParams();
    axios({
      url: this.addPageViewUrl,
      method: 'POST',
      data: {
        page_title: document.title,
        ...payload,
        ...params,
      },
    }).catch((e) => {
      Log.log(`report error: ${e}`);
    });
  },

  // 接口请求上报
  sendXhrEvent(data) {
    const payload = this.getCommonParams();
    if (this.isWhiteUrl(data.url)) return

    axios({
      url: this.addGatewayUrl,
      method: 'POST',
      data: {
        ...payload,
        ...data,
      },
    }).catch((e) => {
      Log.log(`report error: ${e}`);
    });
  },
};

export default sman;
